package cn.roadtrip.utilmodule.util;


import cn.hutool.core.date.DateUtil;
import cn.roadtrip.umongomodule.mongo.DBTemplateHelper;
import cn.roadtrip.uredismodule.util.RedisUtil;
import com.google.gson.Gson;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.model.Indexes;
import jakarta.annotation.PreDestroy;
import lombok.Builder;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.bson.Document;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

import static cn.roadtrip.utilmodule.util.AccessMVCThreadSet.CurrentThreadLocal;

@Aspect
@Component
@Slf4j
@ConditionalOnClass({cn.roadtrip.umongomodule.mongo.DBTemplateHelper.class, cn.roadtrip.uredismodule.util.RedisUtil.class})
public class ValidControllerFilter {

    private JWTHelper jwtHelper;

    private RedisUtil redisUtil;

    private DBTemplateHelper mongoHelper;

    public ValidControllerFilter(
            JWTHelper jwtHelper,
            RedisUtil redisUtil,
            DBTemplateHelper mongoHelper
    ) {
        this.jwtHelper = jwtHelper;
        this.redisUtil = redisUtil;
        this.mongoHelper = mongoHelper;
    }

    @Value("${devmod:dev}")
    private String devmod;


    @Pointcut("@annotation(ValidControllerAnn)")
    private void _check() {
    }

    private final ReentrantLock lock = new ReentrantLock();
    /**
     * 线程
     */
    private ExecutorService logRunnableService = Executors.newFixedThreadPool(5);

    @Data
    @Builder
    public static class UserLogDetail {
        /**
         * 用户ID
         */
        private String userId;
        /**
         * 身份
         */
        private String aut;
        /**
         * 创建时间
         */
        private Long createTime;
        /**
         * 标记日期
         */
        private Long markDate;
        /**
         * 传递参数
         */
        private String postDatas;
        /**
         * 方法标签
         */
        private String methodSign;
    }

    @PreDestroy
    public void destoryExecutors() {
        try {
            logRunnableService.shutdown();
        } catch (Exception e) {

        }
    }

    private final String logDb = "AccessLog";

    private ReentrantLock logLock = new ReentrantLock();

    /**
     * 标记访问日志
     *
     * @param userId
     * @param datas
     * @param methodSign
     */
    public void accessLog(
            String userId,
            String aut,
            Object[] datas,
            String methodSign
    ) {
        logRunnableService.execute(new Runnable() {
            @Override
            public void run() {
                Long now = System.currentTimeMillis();
                String year = DateUtil.year(new Date(now)) + "";
                int _month = DateUtil.month(new Date(now)) + 1;
                String month = "A";
                if (_month < 10) {
                    month = month + "0";
                }
                month = month + _month;
                String dbName = logDb + ":" + year;
                MongoTemplate mongoTemplate = mongoHelper.createTemplate(dbName);
                UserLogDetail userLogDetail = UserLogDetail.builder()
                        .userId(userId)
                        .createTime(now)
                        .aut(aut)
                        .methodSign(methodSign)
                        .markDate(DateUtil.parse(DateUtil.format(new Date(now), "yyyy-MM-dd"), "yyyy-MM-dd").getTime())
                        .build();
                Gson gson = new Gson();
                String data = "{}";
                try {
                    data = gson.toJson(datas);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                userLogDetail.setPostDatas(data);
                try {
                    mongoTemplate.getCollection(month).insertOne(Document.parse(gson.toJson(userLogDetail)));
                } catch (Exception e) {
                    e.printStackTrace();
                }
                try {
                    logLock.tryLock(200, TimeUnit.MILLISECONDS);
                    if (!redisUtil.existKey(dbName + month)) {
                        redisUtil.set(dbName + month, "true", null);
                        MongoCollection mongoCollection = mongoHelper.createTemplate(dbName).getCollection(month);
                        mongoCollection.createIndex(
                                Indexes.compoundIndex(
                                        Indexes.descending("userId"),
                                        Indexes.descending("aut"),
                                        Indexes.descending("markDate"),
                                        Indexes.descending("createTime"),
                                        Indexes.descending("methodSign")
                                )
                        );
                        mongoCollection.createIndex(
                                Indexes.compoundIndex(
                                        Indexes.descending("methodSign"),
                                        Indexes.descending("markDate"),
                                        Indexes.descending("createTime"),
                                        Indexes.descending("aut"),
                                        Indexes.descending("userId")
                                )
                        );
                    }
                } catch (Exception e) {

                } finally {
                    logLock.unlock();
                }
            }
        });
    }

    @Around("_check()")
    public Object watchControllerAdvise(ProceedingJoinPoint pjp) throws Throwable {
        if (devmod.equals("dev")) {
            return pjp.proceed(pjp.getArgs());
        }

        MethodSignature methodSignature = ((MethodSignature) pjp.getSignature());
        Method method = methodSignature.getMethod();
        Object returnBean = null;
        String token = null;
        String clientCode = null;
        String control_code = null;
        try {
            AccessMVCThreadSet.AccessControllerHeader header = CurrentThreadLocal.get();
            if (header == null) {
                log.info("CurrentThreadLocal.get() 为空");
                return DataBean.err("currentThreadLocal-null", "线程local为空");
            }
            /**
             * 验证token
             */
            token = header.getUtoken();
            clientCode = header.getClientCode();

            String userId = null;
            String agent = null;
            try {
                TokenValue tokenValue = jwtHelper.parseToken(token);
                userId = tokenValue.getUserId();
                agent = tokenValue.getAgent();
            } catch (Exception e) {
                e.printStackTrace();
                return DataBean.err("tokenparse-err", "token解析错误", e.getMessage());
            }

            /**
             * 验证重复操作
             */
            lock.tryLock(30, TimeUnit.SECONDS);
            //验证是否重复操作.. client-aut-user-method
            control_code = clientCode + ":" + header.getAut() + ":" + userId + ":" + method.getName();
            if (redisUtil.existKey(control_code)) {
                lock.unlock();
                return DataBean.err("controll-repeat", "重复操作");
            }
            redisUtil.set(control_code, "true", 30l);
            lock.unlock();
            try {
                accessLog(
                        userId,
                        header.getAut(),
                        pjp.getArgs(),
                        pjp.getTarget().getClass().getName() + "." + methodSignature.getName()
                );
            } catch (Exception e) {
                e.printStackTrace();
            }
            try {
                Object robj = pjp.proceed(pjp.getArgs());
                DataBean db = (DataBean) robj;
                if (db.isSuc()) {
                    token = jwtHelper.freshTokenTime(userId, header.getDirect(), clientCode, agent);
                    db.setToken(token);
                }
                return db;
            } catch (Exception e) {
                e.printStackTrace();
                throw new RuntimeException(e);
            }
        } finally {
            redisUtil.deleteKey(control_code);
            CurrentThreadLocal.remove();
        }
    }
}